import * as BL from '../index';


/**
 * 资源加载（解析）
 * @param scene 所在的场景
 */
class AssetsLoad {
    public readonly id = BL.Utils.id;
    public readonly taskInfos: Array<BL.AbstractAssetTaskInfo> = [];
    public readonly scene: BL.Scene;
    public useLoadingScreen = true;

    private _taskInfos: Array<BL.AbstractAssetTaskInfo>;
    private _manager!: BL.AssetsManager;
    private _progress = 0;
    private _total = Number.POSITIVE_INFINITY;
    private _onStart: { (): void } | undefined;
    private _onProgress: { (progress: number): void } | undefined;
    private _onTaskSuccess: { (task: BL.AbstractAssetTask): void } | undefined;
    private _onFinish: { (): void } | undefined;
    private _onTaskError: { (task: BL.AbstractAssetTask): void } | undefined;

    constructor(scene: BL.Scene) {
        this.scene = scene;
        this._manager = new BL.AssetsManager(scene);
        this._taskInfos = [];

        const mngr = this._manager;
        mngr.autoHideLoadingUI = false;
        mngr.onProgress = (remainingCount: number, totalCount: number, task: BL.AbstractAssetTask): void => {
            if (this._onProgress) this._onProgress(this.progress);
        };
        mngr.onTaskSuccess = async (task: BL.AbstractAssetTask): Promise<void> => {
            this._progress += 0.9;
            await this.processTask(task);
            if (this._onTaskSuccess) this._onTaskSuccess(task);
            this.checkLoadedAndResolved();
        };
        mngr.onFinish = async (tasks: Array<BL.AbstractAssetTask>): Promise<void> => {
            for (const taskInfo of this._taskInfos) {
                if (taskInfo.task) continue;
                await this.processTask(taskInfo);
                this.checkLoadedAndResolved();
            }
        };
        mngr.onTaskError = (task: BL.AbstractAssetTask): void => {
            if (this._onTaskError) this._onTaskError(task);
        };
    }

    /**
     * 获取加载进度（按加载资源数量计的）
     */
    public get progress(): number {
        return this._progress / this._total;
    }

    /**
     * 获取模型资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getMeshTaskInfo(rawUrl: string): BL.GLTFMeshTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== BL.TASK_INFO_TYPES.GLTF_MESH) continue;
            if (taskInfo.rawUrl === rawUrl && taskInfo.loaded) return taskInfo as BL.GLTFMeshTaskInfo;
        }
        return null;
    }

    /**
     * 获纹理型资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getTextureTaskInfo(rawUrl: string): BL.TextureTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== BL.TASK_INFO_TYPES.TEXTURE) continue;
            if (taskInfo.rawUrl === rawUrl && taskInfo.loaded) return taskInfo as BL.TextureTaskInfo;
        }
        return null;
    }

    /**
     * 获取六面立方体纹理资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getCubeTextureTaskInfo(rawUrl: string): BL.CubeTextureTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== BL.TASK_INFO_TYPES.CUBE_TEXTURE) continue;
            if (taskInfo.rawUrl === rawUrl && taskInfo.loaded) return taskInfo as BL.CubeTextureTaskInfo;
        }
        return null;
    }

    /**
     * 获取球面投影立方体纹理资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getEquiRectangularCubeTextureTaskInfo(rawUrl: string): BL.EquiRectangularCubeTextureTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== BL.TASK_INFO_TYPES.EQUI_RECTANGULAR_CUBE_TEXTURE) continue;
            if (taskInfo.rawUrl === rawUrl && taskInfo.loaded) return taskInfo as BL.EquiRectangularCubeTextureTaskInfo;
        }
        return null;
    }

    /**
     * 获取高动态范围立方体纹理资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getHDRCubeTextureTaskInfo(rawUrl: string): BL.HDRCubeTextureTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== BL.TASK_INFO_TYPES.HDR_CUBE_TEXTURE) continue;
            if (taskInfo.rawUrl === rawUrl && taskInfo.loaded) return taskInfo as BL.HDRCubeTextureTaskInfo;
        }
        return null;
    }

    /**
     * 获取音频资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getAudioTaskInfo(rawUrl: string): BL.AudioTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== BL.TASK_INFO_TYPES.AUDIO) continue;
            if (taskInfo.rawUrl === rawUrl && taskInfo.loaded) return taskInfo as BL.AudioTaskInfo;
        }
        return null;
    }

    /**
     * 获取视频纹理资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getVideoTextureTaskInfo(rawUrl: string): BL.VideoTextureTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== BL.TASK_INFO_TYPES.VIDEO) continue;
            if (taskInfo.rawUrl === rawUrl && taskInfo.loaded) return taskInfo as BL.VideoTextureTaskInfo;
        }
        return null;
    }

    /**
     * 获取数据资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getDataTaskInfo(rawUrl: string): BL.DataTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== BL.TASK_INFO_TYPES.DATA) continue;
            if (taskInfo.rawUrl === rawUrl && taskInfo.loaded) return taskInfo as BL.DataTaskInfo;
        }
        return null;
    }

    public release(rawUrl: string): void {
        for (let i = 0; i < this.taskInfos.length; i++) {
            if (this.taskInfos[i].rawUrl === rawUrl) {
                this.taskInfos[i].dispose();
                this.taskInfos.splice(i, 1);
            }
        }
    }

    /**
     * 异步加载单个资源
     * @param taskInfo 资源任务信息 
     * @param onStart 开始加载回调函数
     * @param onFinish 加载成功完成回调函数
     * @param onError 加载失败回调函数
     */
    public loadAsync(taskInfo: BL.AbstractAssetTaskInfo, onStart?: { (): void }, onFinish?: { (): void }, onError?: { (task: BL.AbstractAssetTask): void }): void {
        let exst = false;
        let tkInfo = taskInfo;
        for (const tf of this.taskInfos) {
            if (taskInfo.compare(tf)) {
                exst = true;
                tkInfo = tf;
                break;
            }
        }
        if (tkInfo.loaded) {
            if (onStart) onStart();
            if (onFinish) onFinish();
            return;
        }
        const mgr = new BL.AssetsManager(this.scene);
        taskInfo.setTask(mgr);
        if (!exst) this.taskInfos.push(taskInfo);
        if (onStart) onStart();
        if (taskInfo instanceof BL.VideoTextureTaskInfo) {
            mgr.onFinish = async () => {
                await this.processTask(taskInfo);
                taskInfo.texture!.onLoadObservable.addOnce(() => {
                    if (onFinish) onFinish();
                });
            };
        } else {
            mgr.onTaskSuccess = async (tsk: BL.AbstractAssetTask): Promise<void> => {
                await this.processTask(tsk);
                if (onFinish) onFinish();
            };
        }
        mgr.onTaskError = (task: BL.AbstractAssetTask): void => {
            if (onError) onError(task);
        };
        mgr.useDefaultLoadingScreen = this.useLoadingScreen;
        mgr.loadAsync();
    }

    /**
     * 加载资源
     * @param taskInfos 资源任务信息
     * @param onStart 开始加载回调函数
     * @param onProgress 加载中回调函数
     * @param onTaskSuccess 单个任务完成回调函数
     * @param onFinish 加载完成回调函数
     * @param onTaskError 异常回调函数
     */
    public load(taskInfos: BL.ILoose<BL.AbstractAssetTaskInfo> | Array<BL.AbstractAssetTaskInfo>,
        onStart?: { (): void },
        onProgress?: { (progress: number): void },
        onTaskSuccess?: { (task: BL.AbstractAssetTask): void },
        onFinish?: { (): void },
        onTaskError?: { (task: BL.AbstractAssetTask): void },
    ): void {
        this._onStart = onStart;
        this._onProgress = onProgress;
        this._onTaskSuccess = onTaskSuccess;
        this._onFinish = onFinish;
        this._onTaskError = onTaskError;
        this._taskInfos.length = 0;
        const manager = this._manager;
        manager.useDefaultLoadingScreen = this.useLoadingScreen;
        if (Array.isArray(taskInfos)) {
            for (const taskInfo of taskInfos) {
                let exst = false;
                let tk = taskInfo;
                for (const tf of this.taskInfos) {
                    if (taskInfo.compare(tf)) {
                        exst = true;
                        tk = tf;
                        break;
                    }
                }
                if (!tk.loaded) {
                    tk.setTask(manager);
                    this._taskInfos.push(tk);
                    if (!exst) this.taskInfos.push(tk);
                }
            }
        } else {
            for (let key in taskInfos) {
                const taskInfo = taskInfos[key];
                let exst = false;
                let tk = taskInfo;
                for (const tf of this.taskInfos) {
                    if (taskInfo.compare(tf)) {
                        exst = true;
                        tk = tf;
                        break;
                    }
                }
                if (!tk.loaded) {
                    tk.setTask(manager);
                    this._taskInfos.push(tk);
                    if (!exst) this.taskInfos.push(tk);
                }
            }
        }
        if (this._taskInfos.length === 0) {
            if (this._onStart) this._onStart();
            if (this._onFinish) this._onFinish();
            return;
        }
        this._total = this._taskInfos.length;
        this._progress = 0;
        if (this._onStart) this._onStart();
        manager.load();
    }

    /**
     * 初始化资源对象
     * @param task 资源任务，或任务信息
     */
    private async processTask(task: BL.AbstractAssetTask | BL.AbstractAssetTaskInfo): Promise<void> {
        let targetInfo = task as BL.AbstractAssetTaskInfo;
        if (!(task as BL.AbstractAssetTaskInfo).rawUrl) {
            for (const taskInfo of this.taskInfos) {
                if (taskInfo.task?.name === (task as BL.AbstractAssetTask).name) {
                    targetInfo = taskInfo;
                    break;
                }
            }
        }

        switch (targetInfo.type) {
            case BL.TASK_INFO_TYPES.GLTF_MESH:
                const _task = task as BL.MeshAssetTask;
                const _taskInfo = targetInfo as BL.GLTFMeshTaskInfo;
                _taskInfo.loadedAnimationGroups = _task.loadedAnimationGroups;
                _taskInfo.loadedMeshes = _task.loadedMeshes;
                _taskInfo.loadedParticleSystems = _task.loadedParticleSystems;
                _taskInfo.loadedSkeletons = _task.loadedSkeletons;
                _taskInfo.loadedTransformNodes = _task.loadedTransformNodes;
                this._progress += 0.1;
                break;
            case BL.TASK_INFO_TYPES.TEXTURE:
                (targetInfo as BL.TextureTaskInfo).texture = (task as BL.TextureAssetTask).texture;
                this._progress += 0.1;
                break;
            case BL.TASK_INFO_TYPES.CUBE_TEXTURE:
                (targetInfo as BL.CubeTextureTaskInfo).texture = (task as BL.CubeTextureAssetTask).texture;
                this._progress += 0.1;
                break;
            case BL.TASK_INFO_TYPES.EQUI_RECTANGULAR_CUBE_TEXTURE:
                (targetInfo as BL.EquiRectangularCubeTextureTaskInfo).texture = (task as BL.EquiRectangularCubeTextureAssetTask).texture;
                this._progress += 0.1;
                break;
            case BL.TASK_INFO_TYPES.HDR_CUBE_TEXTURE:
                (targetInfo as BL.HDRCubeTextureTaskInfo).texture = (task as BL.HDRCubeTextureAssetTask).texture;
                this._progress += 0.1;
                break;
            case BL.TASK_INFO_TYPES.AUDIO:
                (targetInfo as BL.AudioTaskInfo).sound = await this.generateSound(targetInfo.rawUrl, task as BL.BinaryFileAssetTask);
                break;
            case BL.TASK_INFO_TYPES.DATA:
                (targetInfo as BL.DataTaskInfo).data = (task as BL.BinaryFileAssetTask).data;
                this._progress += 0.1;
                break;
            case BL.TASK_INFO_TYPES.VIDEO:
                const _vInfo = targetInfo as BL.VideoTextureTaskInfo;
                _vInfo.texture = this.generateVideoTexture(_vInfo);
                break;
        }
        targetInfo.loaded = true;
    }

    /**
     * 创建视频纹理对象
     * @param info 任务信息
     * @returns 视频纹理
     */
    private generateVideoTexture(info: BL.VideoTextureTaskInfo): BL.VideoTexture {
        const videoTexture = new BL.VideoTexture(info.rawUrl, info.dataUrl!, this.scene, false, false, undefined,
            {
                autoPlay: false,
                muted: true,
            }
        );

        videoTexture.video.load();
        videoTexture.onLoadObservable.addOnce(() => {
            this._progress += 1;
        });
        return videoTexture;
    }

    /**
     * 创建音频对象
     * @param name 名称
     * @param task 资源任务
     * @returns 音频对象
     */
    private async generateSound(name: string, task: BL.BinaryFileAssetTask): Promise<BL.AbstractSound> {
        const aryBfr = task.data;
        const sound = await BL.CreateSoundAsync(name, aryBfr, { autoplay: false }, BL.BabylonContainer.audioEngine);

        this._progress += 0.1;

        return sound;
    }

    /**
     * 检查资源加载和创建是否完成，并在完成时执行设置的回调函数
     */
    private checkLoadedAndResolved() {
        const ref = this._total - 0.001;
        if (this._progress < ref) return;
        if (this._onFinish) this._onFinish();
    }

}

export { AssetsLoad };